Skip to content

Backport #4216 to 4.x: make Array.prototype.concat spread host arrays and lists - #4219

Merged
lahma merged 1 commit into
sebastienros:4.xfrom
lahma:backport/4x-interop
Oct 5, 2026
Merged

lahma merged 1 commit into
sebastienros:4.xfrom
lahma:backport/4x-interop

Conversation

@lahma

@lahma lahma commented Oct 5, 2026

Copy link
Copy Markdown
Collaborator

This backports one fix from main to 4.x. A second candidate was skipped because 4.x does not have the code it changes.

Commit Main PR What it fixes
5a0b45b #4216 (0a6288a) Array.prototype.concat nested host arrays and lists instead of spreading them (#4201). Since 4.14, ArrayConversionMode.LiveView has been the default, and under it engine.SetValue("a", new[] { x }); a.concat(b) returned a two-element array holding the two wrappers. The same happened with any List<T>.

Adapted for 4.x

  • On 4.x, IsConcatSpreadable falls back to IsArray(), not main's IsSpecArray(). The fix keeps that call and adds the same wrapper test after it: IsArray() || this is ObjectWrapper { IsArrayLike: true, HasIndexedElements: true }. On 4.x, ObjectWrapper.IsArrayLike and HasIndexedElements match main, including the TypeResolver member-filter gate in HasIndexedElements. A queue, a stack or a set still is not spread. A JsArray still never reaches the type test.
  • The test file moved to Jint.Tests/Runtime/ClrCollectionConcatTests.cs (4.x has no Runtime/Interop folder), and its [Test] attributes became xUnit [Fact]s. 4.x has no SpecAnchors.txt, so main's anchor-list change is not included.
  • No public API or default changes.

Evidence

The seven tests in ClrCollectionConcatTests were run first without the fix. ConcatSpreadsTwoClrArraysOfExpandoObjects is the host code from #4201, as written in the issue.

TFM Unfixed 4.x With the fix
net10.0 6 failed, 1 passed 7 passed
net472 6 failed, 1 passed 7 passed

The test that passes without the fix is ConcatDoesNotSpreadACollectionWithoutIndexedElements. It checks that a Queue<T> stays a single element, so it should pass both before and after the fix.

Skipped

Test totals (Release, after the fix)

  • Jint.Tests on net10.0: 7662 passed, 4 skipped, 0 failed
  • Jint.Tests on net472: 7577 passed, 4 skipped, 0 failed
  • Jint.Tests.PublicInterface on net10.0: 1903 passed, 9 skipped, 0 failed
  • Jint.Tests.PublicInterface on net472: 1895 passed, 9 skipped, 0 failed
  • Jint.Tests.Test262, full run on net10.0: 102509 passed, 175 skipped, 0 failed

The public API Verify snapshots are unchanged; the working tree was clean after the runs.

🤖 Generated with Claude Code

…script sees as arrays in Array.prototype.concat

IsConcatSpreadable falls back to IsArray, which a host wrapper is not, so a
live view over a CLR array (ArrayConversionMode.LiveView, the default on 4.x)
or a List<T> was appended as a single element. A wrapper that exposes its
target's elements by index is now spreadable.

Adapted for 4.x: the fallback there is IsArray() rather than main's
IsSpecArray(), and the test lives in Jint.Tests/Runtime with xUnit [Fact]s.

Fixes sebastienros#4201.

Adapted from 0a6288a

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@lahma
lahma merged commit ebddb3b into sebastienros:4.x Oct 5, 2026
5 checks passed
@lahma
lahma deleted the backport/4x-interop branch October 5, 2026 09:58
bgarcevic pushed a commit to bgarcevic/tomix-cli that referenced this pull request Oct 12, 2026
Updated [Jint](https://github.com/sebastienros/jint) from 4.16.4 to
4.17.0.

<details>
<summary>Release notes</summary>

_Sourced from [Jint's
releases](https://github.com/sebastienros/jint/releases)._

## 4.17.0

Jint 4.17.0 is a release from the `4.x` branch. It backports correctness
and conformance fixes from `main` (four of them for scripts that could
end the host process), linear string building, and **two new opt-in APIs
for hosts**. That is why it is a minor release rather than a patch.
Nothing in it changes an existing API or an existing default, so it is a
drop-in update from 4.16.4.

### Highlights

**A pooled engine can let go of a finished request's objects.** Hosts
that reuse one engine across many evaluations (`CaptureGlobalSnapshot`
once, `RestoreGlobalSnapshot` between runs) keep the interpreter's
warmed caches, and that is the point of reusing it. But a warmed call
site remembers the last function it called, and a warmed member read
remembers the last object it read from. In a pooled web host, that can
be a delegate built from a finished request's services, which then stay
alive until the same script runs again. The new
`Engine.Advanced.DiscardInterpreterCaches()` drops those caches, so the
objects can be collected. The engine, its intrinsics and its global
object stay as they are. The next run of each script rebuilds its cache,
which costs roughly what a first run on a new engine costs, minus
building the engine. A good place to call it is when an engine has been
idle for a while, rather than after every use (#​4229, from #​4227).

**A host can carry its own state across `await`.** Jint now runs
ECMAScript's HostMakeJobCallback and HostCallJobCallback steps for every
promise reaction, every thenable job and the `FinalizationRegistry`
cleanup callback. A host can take part through the new
`Options.Host.JobCallbacks`, which accepts a `JobCallbackHooks` with
`Capture`, `Enter` and `Exit` callbacks. That lets each branch of a
`Promise.all` fan-out keep its own host context, such as a logging scope
or ambient request data, through every `await` (#​4231, from #​4230;
issue #​4200). The default is no hooks, in which case each promise
registration costs one null check and nothing a script can observe
changes.

**Four more ways for a script to end the host process are closed.**
- **ShadowRealm.** A failure crossing a ShadowRealm boundary threw its
`TypeError` copy from inside the `catch` that was handling the original
failure. That added a nested exception dispatch at every hop, and no
stack check could see it. Even with the stack-overflow guard on, a long
chain of wrapped functions ended the process on the way back out, and a
three-line script with a `name` getter needed no chain at all. Building
that copy also ran script (`toString`, getters, proxy traps); it no
longer does. A failed `importValue` now reports the import failure
instead of an unrelated error (#​4220, from #​4176 and #​4183).
- **`instanceof` and `eval`.** `instanceof` now checks the native stack
before calling a custom `Symbol.hasInstance` method, and `eval` checks
it before parsing. A self-referencing `Symbol.hasInstance`, or `var s =
'eval(s)'; eval(s)`, now raises a `RangeError` the script can catch
instead of ending the process. On the `MaxExecutionStackCount` path,
`eval` also stopped hopping to a new thread at each overflow without
ever throwing. `instanceof` over a bound function now consults every
bound link's own `Symbol.hasInstance`, as the specification requires
(#​4221, from #​4172, #​4184 and #​4187).
- **These checks follow 4.x's existing rule.** Like every other stack
check on 4.x, they are active only when
`Options.Constraints.StackOverflowGuard` is on, so turn it on for any
script you do not fully trust.

**Building a string with `s = s + x` is linear.** A `+` whose result
reaches 512 characters now produces a deferred string instead of copying
both sides. Building with `s = s + x`, `s = x + s` and `s = s + a + b`
therefore becomes linear, as `s += x` already was. Short concatenations
take a separate path and pay nothing for this. The deferred string is
safe to read from several engines that share one `Prepared<Script>`, and
it is charged to `LimitMemory` when it is built, so a memory limit still
bounds it. Binding an already-bound function also no longer copies its
whole `"bound …"` name at every level (#​4160, from #​3386, #​3571,
#​4130, #​4164 and #​4173).

**`Array.prototype.concat` spreads host arrays and lists.** Since 4.14,
CLR arrays and lists reach script as live views by default. `concat`
added such a view as a single element instead of spreading its items, so
`a.concat(b)` returned the two wrappers. It now spreads anything script
sees as an array (#​4219, from #​4216; issue #​4201).

**`TypedArray.prototype.with` survives a shrinking coercion.** When
converting the index or the value made a resizable buffer shorter,
`with` copied past its new end and let a CLR `ArgumentException` escape.
It now copies only what the buffer still holds (#​4218, from #​4157).

### Verification

Every backport was verified failing-first: its tests were run against
the unfixed `4.x` tree on .NET 10 and .NET Framework 4.7.2, then with
the change. The release candidate was measured against 4.16.4 on
SunSpider and Dromaeo in six paired rounds, alternating which build ran
first in each round. The four Dromaeo `ObjectRegExp` rows ran **12–15%
faster** and SunSpider's `3d-raytrace` 2.3% faster. No row regressed by
1% or more; the two rows whose interval did not include zero (one
Dromaeo `Cube` variant, +0.70%, and SunSpider's `controlflow-recursive`,
+0.88%) are within this machine's run-to-run noise.

## What's Changed
* Backport culture-independent overload-scoring fixture to 4.x by
@​PrzemyslawKlys in sebastienros/jint#4215
* Backport #​4216 to 4.x: make Array.prototype.concat spread host arrays
and lists by @​lahma in sebastienros/jint#4219
* Backport #​4176, #​4183 to 4.x: keep a ShadowRealm failure copy from
killing the host or running script by @​lahma in
sebastienros/jint#4220
* Backport #​3386, #​3571, #​4130, #​4164 and #​4173 to 4.x: linear
string building, safe across engines and charged to LimitMemory by
@​lahma in sebastienros/jint#4160
* Backport #​4172, #​4184, #​4187 to 4.x: stop instanceof and eval
recursions from killing the host by @​lahma in
sebastienros/jint#4221
* Backport #​4157 to 4.x: Fix TypedArray.prototype.with after a
shrinking coercion by @​lahma in
sebastienros/jint#4218
* Backport #​4227 to 4.x: Let a reused engine discard the host objects
its warmed caches hold by @​lahma in
sebastienros/jint#4229
* Backport #​4230 to 4.x: Let a host carry its own state across await
through HostMakeJobCallback by @​lahma in
sebastienros/jint#4231


**Full Changelog**:
sebastienros/jint@v4.16.4...v4.17.0


Commits viewable in [compare
view](sebastienros/jint@v4.16.4...v4.17.0).
</details>

Updated
[Microsoft.AnalysisServices](https://docs.microsoft.com/en-us/sql/analysis-services/tabular-models/tabular-models-ssas?view=sql-server-2017)
from 19.117.0 to 19.118.3.

Updated
[Microsoft.AnalysisServices.AdomdClient](https://docs.microsoft.com/en-us/sql/analysis-services/tabular-models/tabular-models-ssas?view=sql-server-2017)
from 19.117.0 to 19.118.3.

Updated
[xunit.runner.visualstudio](https://github.com/xunit/visualstudio.xunit)
from 4.0.0 to 4.0.1.

<details>
<summary>Release notes</summary>

_Sourced from [xunit.runner.visualstudio's
releases](https://github.com/xunit/visualstudio.xunit/releases)._

## 4.0.1

Release notes: https://xunit.net/releases/visualstudio/4.0.1

Commits viewable in [compare
view](xunit/visualstudio.xunit@4.0.0...4.0.1).
</details>

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
PatrickSt1991 pushed a commit to Apps2Samsung/Apps2Samsung that referenced this pull request Oct 13, 2026
Updated [Jint](https://github.com/sebastienros/jint) from 4.16.4 to
4.17.0.

<details>
<summary>Release notes</summary>

_Sourced from [Jint's
releases](https://github.com/sebastienros/jint/releases)._

## 4.17.0

Jint 4.17.0 is a release from the `4.x` branch. It backports correctness
and conformance fixes from `main` (four of them for scripts that could
end the host process), linear string building, and **two new opt-in APIs
for hosts**. That is why it is a minor release rather than a patch.
Nothing in it changes an existing API or an existing default, so it is a
drop-in update from 4.16.4.

### Highlights

**A pooled engine can let go of a finished request's objects.** Hosts
that reuse one engine across many evaluations (`CaptureGlobalSnapshot`
once, `RestoreGlobalSnapshot` between runs) keep the interpreter's
warmed caches, and that is the point of reusing it. But a warmed call
site remembers the last function it called, and a warmed member read
remembers the last object it read from. In a pooled web host, that can
be a delegate built from a finished request's services, which then stay
alive until the same script runs again. The new
`Engine.Advanced.DiscardInterpreterCaches()` drops those caches, so the
objects can be collected. The engine, its intrinsics and its global
object stay as they are. The next run of each script rebuilds its cache,
which costs roughly what a first run on a new engine costs, minus
building the engine. A good place to call it is when an engine has been
idle for a while, rather than after every use (#​4229, from #​4227).

**A host can carry its own state across `await`.** Jint now runs
ECMAScript's HostMakeJobCallback and HostCallJobCallback steps for every
promise reaction, every thenable job and the `FinalizationRegistry`
cleanup callback. A host can take part through the new
`Options.Host.JobCallbacks`, which accepts a `JobCallbackHooks` with
`Capture`, `Enter` and `Exit` callbacks. That lets each branch of a
`Promise.all` fan-out keep its own host context, such as a logging scope
or ambient request data, through every `await` (#​4231, from #​4230;
issue #​4200). The default is no hooks, in which case each promise
registration costs one null check and nothing a script can observe
changes.

**Four more ways for a script to end the host process are closed.**
- **ShadowRealm.** A failure crossing a ShadowRealm boundary threw its
`TypeError` copy from inside the `catch` that was handling the original
failure. That added a nested exception dispatch at every hop, and no
stack check could see it. Even with the stack-overflow guard on, a long
chain of wrapped functions ended the process on the way back out, and a
three-line script with a `name` getter needed no chain at all. Building
that copy also ran script (`toString`, getters, proxy traps); it no
longer does. A failed `importValue` now reports the import failure
instead of an unrelated error (#​4220, from #​4176 and #​4183).
- **`instanceof` and `eval`.** `instanceof` now checks the native stack
before calling a custom `Symbol.hasInstance` method, and `eval` checks
it before parsing. A self-referencing `Symbol.hasInstance`, or `var s =
'eval(s)'; eval(s)`, now raises a `RangeError` the script can catch
instead of ending the process. On the `MaxExecutionStackCount` path,
`eval` also stopped hopping to a new thread at each overflow without
ever throwing. `instanceof` over a bound function now consults every
bound link's own `Symbol.hasInstance`, as the specification requires
(#​4221, from #​4172, #​4184 and #​4187).
- **These checks follow 4.x's existing rule.** Like every other stack
check on 4.x, they are active only when
`Options.Constraints.StackOverflowGuard` is on, so turn it on for any
script you do not fully trust.

**Building a string with `s = s + x` is linear.** A `+` whose result
reaches 512 characters now produces a deferred string instead of copying
both sides. Building with `s = s + x`, `s = x + s` and `s = s + a + b`
therefore becomes linear, as `s += x` already was. Short concatenations
take a separate path and pay nothing for this. The deferred string is
safe to read from several engines that share one `Prepared<Script>`, and
it is charged to `LimitMemory` when it is built, so a memory limit still
bounds it. Binding an already-bound function also no longer copies its
whole `"bound …"` name at every level (#​4160, from #​3386, #​3571,
#​4130, #​4164 and #​4173).

**`Array.prototype.concat` spreads host arrays and lists.** Since 4.14,
CLR arrays and lists reach script as live views by default. `concat`
added such a view as a single element instead of spreading its items, so
`a.concat(b)` returned the two wrappers. It now spreads anything script
sees as an array (#​4219, from #​4216; issue #​4201).

**`TypedArray.prototype.with` survives a shrinking coercion.** When
converting the index or the value made a resizable buffer shorter,
`with` copied past its new end and let a CLR `ArgumentException` escape.
It now copies only what the buffer still holds (#​4218, from #​4157).

### Verification

Every backport was verified failing-first: its tests were run against
the unfixed `4.x` tree on .NET 10 and .NET Framework 4.7.2, then with
the change. The release candidate was measured against 4.16.4 on
SunSpider and Dromaeo in six paired rounds, alternating which build ran
first in each round. The four Dromaeo `ObjectRegExp` rows ran **12–15%
faster** and SunSpider's `3d-raytrace` 2.3% faster. No row regressed by
1% or more; the two rows whose interval did not include zero (one
Dromaeo `Cube` variant, +0.70%, and SunSpider's `controlflow-recursive`,
+0.88%) are within this machine's run-to-run noise.

## What's Changed
* Backport culture-independent overload-scoring fixture to 4.x by
@​PrzemyslawKlys in sebastienros/jint#4215
* Backport #​4216 to 4.x: make Array.prototype.concat spread host arrays
and lists by @​lahma in sebastienros/jint#4219
* Backport #​4176, #​4183 to 4.x: keep a ShadowRealm failure copy from
killing the host or running script by @​lahma in
sebastienros/jint#4220
* Backport #​3386, #​3571, #​4130, #​4164 and #​4173 to 4.x: linear
string building, safe across engines and charged to LimitMemory by
@​lahma in sebastienros/jint#4160
* Backport #​4172, #​4184, #​4187 to 4.x: stop instanceof and eval
recursions from killing the host by @​lahma in
sebastienros/jint#4221
* Backport #​4157 to 4.x: Fix TypedArray.prototype.with after a
shrinking coercion by @​lahma in
sebastienros/jint#4218
* Backport #​4227 to 4.x: Let a reused engine discard the host objects
its warmed caches hold by @​lahma in
sebastienros/jint#4229
* Backport #​4230 to 4.x: Let a host carry its own state across await
through HostMakeJobCallback by @​lahma in
sebastienros/jint#4231


**Full Changelog**:
sebastienros/jint@v4.16.4...v4.17.0


Commits viewable in [compare
view](sebastienros/jint@v4.16.4...v4.17.0).
</details>

[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=Jint&package-manager=nuget&previous-version=4.16.4&new-version=4.17.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant